package com.csx.base.concurrency.aqs.reentrantlock;

import com.csx.base.concurrency.util.TimeUtil;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p> what does this class do ? </p>
 * <p> How does it work ?</p>
 * <p> Why does it do this ?</p>
 *
 * @author cuisongxu
 * @date 2024/12/2 周一 15:05
 */
public class ReentrantLockConditionTest {

    private final ReentrantLock lock = new ReentrantLock(false);

    private final Condition producerCondition = lock.newCondition();

    private final Condition consumerCondition = lock.newCondition();

    private int productNum = 0;

    public void test() {

        Thread producer1 = new Thread(new Producer(), "producer1");
        Thread producer2 = new Thread(new Producer(), "producer2");
        Thread consumer1 = new Thread(new Consumer(), "consumer1");
        Thread consumer2 = new Thread(new Consumer(), "consumer2");
        Thread consumer3 = new Thread(new Consumer(), "consumer3");

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();

        try {
            producer1.join();
            producer2.join();
            consumer1.join();
            consumer2.join();
            consumer3.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("执行结束");
    }

    class Consumer implements Runnable {

        private void consume() {
            lock.lock();
            String nowTimeStr = TimeUtil.getNowTimeStr();
            try {
                // 产品为0,阻塞等待
                while(productNum <= 0) {
                    nowTimeStr = TimeUtil.getNowTimeStr();
                    System.out.println(nowTimeStr + " " + Thread.currentThread().getName() + " 等待产品生产");
                    consumerCondition.await();
                }

                productNum--;
                nowTimeStr = TimeUtil.getNowTimeStr();
                System.out.println(nowTimeStr + " " + Thread.currentThread().getName() + "消费了产品,产品数量剩余: " + productNum);
                // 通知生产线程生产
                producerCondition.signal();

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }
        @Override
        public void run() {
            while(true) {
                consume();
                try {
                    Random random = new Random();
                    // 模拟消费耗时
                    int gap = random.nextInt(500);
                    TimeUnit.MILLISECONDS.sleep(gap);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    class Producer implements Runnable {

        private void produce() {
            lock.lock();
            String nowTimeStr = TimeUtil.getNowTimeStr();
            try {
                // 产品满了,阻塞等待
                int productNumberLimit = 10;
                while(productNum >= productNumberLimit) {
                    nowTimeStr = TimeUtil.getNowTimeStr();
                    System.out.println(nowTimeStr + " " + Thread.currentThread().getName() + "等待产品消费");
                    producerCondition.await();
                }

                productNum++;
                nowTimeStr = TimeUtil.getNowTimeStr();
                System.out.println(nowTimeStr + " " + Thread.currentThread().getName() + "生产了产品，当前产品数量: " + productNum);
                // 通知消费线程消费
                consumerCondition.signal();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }

        @Override
        public void run() {
            while(true) {
                produce();
                try {
                    Random random = new Random();
                    // 模拟产品生产耗时
                    int gap = random.nextInt(200);
                    TimeUnit.MILLISECONDS.sleep(gap);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLockConditionTest reentrantLockConditionTest = new ReentrantLockConditionTest();
        reentrantLockConditionTest.test();
    }
}
